﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace RapidDownloader.Model
{
    public static class DownloadLimiter
    {
        #region Fields

        public static readonly Subject<double> BandWidthUsageSubject = new Subject<double>();

        private static long bytesDownloadedInLastInterval;
        private static long[] bandwidthAverager = new[] { 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, };
        private static double currentWait;

        private static long _MaxBandwidth;

        private static readonly EventWaitHandle pauseHandle = new EventWaitHandle(true, EventResetMode.ManualReset);
        private static readonly object syncRoot = new object();

        private const int BalancerUp = 100; // 50
        private const int BalancerDown = -75;

        #endregion

        #region Constructors

        static DownloadLimiter()
        {
            Observable.Interval(TimeSpan.FromSeconds(1d)).Subscribe(OnReset);
        }

        #endregion

        #region Properties

        public static long MaxBandwidth
        {
            get { return _MaxBandwidth; }
            set
            {
                if (_MaxBandwidth != value)
                {
                    _MaxBandwidth = value;

                    if (value <= 0)
                        pauseHandle.Reset();
                    else
                        pauseHandle.Set();
                }
            }
        }

        #endregion

        #region Public Methods

        public static bool Limit(long length)
        {
            // If paused block the whole thread
            if (MaxBandwidth <= 0)
                pauseHandle.WaitOne();

            bool wait;
            lock (syncRoot)
            {
                bytesDownloadedInLastInterval += length;
                wait = bytesDownloadedInLastInterval > MaxBandwidth;
            }

            if (wait)
                currentWait += BalancerUp;
            else if (currentWait > 0d)
                currentWait = Math.Max(currentWait + BalancerDown, 0d);

            if (currentWait > 0d)
                Thread.Sleep(TimeSpan.FromMilliseconds(currentWait));

            return length > 0;
        }

        #endregion

        #region Private Methods

        private static void OnReset(long n)
        {
            lock (syncRoot)
            {
                if (bandwidthAverager.All(val => val == 0))
                    Enumerable.Range(0, bandwidthAverager.Length).Run(index => bandwidthAverager[index] = bytesDownloadedInLastInterval);
                else
                    bandwidthAverager = bandwidthAverager.Skip(1).Concat(EnumerableEx.Return(bytesDownloadedInLastInterval)).ToArray();

                BandWidthUsageSubject.OnNext(bandwidthAverager.Sum() / (double)bandwidthAverager.Length);

                bytesDownloadedInLastInterval = 0;
            }
        }

        #endregion
    }
}